<!DOCTYPE html>


<html lang="zh-CN">
  

    <head>
      <meta charset="utf-8" />
        
      <meta name="description" content="一个简单的博客" />
      
      <meta
        name="viewport"
        content="width=device-width, initial-scale=1, maximum-scale=1"
      />
      <title>Redis |  空唤晴</title>
  <meta name="generator" content="hexo-theme-ayer">
      
      <link rel="shortcut icon" href="/favicon.ico" />
       
<link rel="stylesheet" href="/dist/main.css">

      
<link rel="stylesheet" href="/css/fonts/remixicon.css">

      
<link rel="stylesheet" href="/css/custom.css">
 
      <script src="https://cdn.staticfile.org/pace/1.2.4/pace.min.js"></script>
       
 

      <link
        rel="stylesheet"
        href="https://cdn.jsdelivr.net/npm/@sweetalert2/theme-bulma@5.0.1/bulma.min.css"
      />
      <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11.0.19/dist/sweetalert2.min.js"></script>

      <!-- mermaid -->
      
      <style>
        .swal2-styled.swal2-confirm {
          font-size: 1.6rem;
        }
      </style>
    <link rel="alternate" href="/atom.xml" title="空唤晴" type="application/atom+xml">
</head>
  </html>
</html>


<body>
  <div id="app">
    
      <canvas class="fireworks"></canvas>
      <style>
        .fireworks {
          position: fixed;
          left: 0;
          top: 0;
          z-index: 99999;
          pointer-events: none;
        }
      </style>
      
      
    <main class="content on">
      <section class="outer">
  <article
  id="post-其它/数据库/Redis"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h1 class="article-title sea-center" style="border-left:0" itemprop="name">
  Redis
</h1>
 

      
    </header>
     
    <div class="article-meta">
      <a href="/2023/05/14/%E5%85%B6%E5%AE%83/%E6%95%B0%E6%8D%AE%E5%BA%93/Redis/" class="article-date">
  <time datetime="2023-05-14T03:01:41.000Z" itemprop="datePublished">2023-05-14</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/%E5%85%B6%E5%AE%83/">其它</a> / <a class="article-category-link" href="/categories/%E5%85%B6%E5%AE%83/%E6%95%B0%E6%8D%AE%E5%BA%93/">数据库</a>
  </div>
  
<div class="word_count">
    <span class="post-time">
        <span class="post-meta-item-icon">
            <i class="ri-quill-pen-line"></i>
            <span class="post-meta-item-text"> 字数统计:</span>
            <span class="post-count">8.5k</span>
        </span>
    </span>

    <span class="post-time">
        &nbsp; | &nbsp;
        <span class="post-meta-item-icon">
            <i class="ri-book-open-line"></i>
            <span class="post-meta-item-text"> 阅读时长≈</span>
            <span class="post-count">33 分钟</span>
        </span>
    </span>
</div>
 
    </div>
      
    <div class="tocbot"></div>




  
    <div class="article-entry" itemprop="articleBody">
       
  <h1 id="Redis"><a href="#Redis" class="headerlink" title="Redis"></a>Redis</h1><hr>
<h2 id="Redis-介绍"><a href="#Redis-介绍" class="headerlink" title="Redis 介绍"></a>Redis 介绍</h2><h3 id="NoSQL-技术"><a href="#NoSQL-技术" class="headerlink" title="NoSQL 技术"></a>NoSQL 技术</h3><p>在实际项目开发中，我们往往需要面对海量用户和高并发的数据请求。MySQL 等传统关系型数据库面临着两大问题：</p>
<ol>
<li>磁盘 IO 速度缓慢，单机读写速度不超过 10000 QPS，当数据库无法及时响应高并发的用户请求，请求积压进而导致数据库瘫痪。</li>
<li>数据关系复杂，扩展性差。不适合大规模集群。</li>
</ol>
<p>因此我们必须引入 NoSQL 技术去解决以上两个问题，以作为关系型数据库的补充。</p>
<h3 id="Redis-数据库"><a href="#Redis-数据库" class="headerlink" title="Redis 数据库"></a>Redis 数据库</h3><p>Redis 是一种<strong>基于内存</strong>的数据库技术。底层采用 C 语言开发，默认端口号 6379。</p>
<ol>
<li>Redis 数据库作为数据缓存，将业务数据直接存储在内存中进行读写，单机读/写速度可达 110000/84000 QPS，可以满足高速响应的需求。</li>
<li>Redis 数据库只负责存储数据，数据之间不具有任何关联，易于扩容和伸缩。</li>
</ol>
<h3 id="Redis-应用场景"><a href="#Redis-应用场景" class="headerlink" title="Redis 应用场景"></a>Redis 应用场景</h3><p>受限于内存的高昂成本，一般我们只使用 Redis 存储高频读写的关键数据。比如：</p>
<ol>
<li><strong>热点数据</strong>：如热点商品信息。</li>
<li><strong>任务队列</strong>：如秒杀队列、抢购队列。</li>
<li><strong>实时更新信息</strong>：如商品排行榜、公交到站信息。</li>
<li><strong>时效性信息</strong>：如手机验证码、session 、 心跳(heartbeat)。</li>
</ol>
<blockquote>
<p>Redis 主要适用于内部系统的高频数据。在线上环境负载极大的情况下，使用 Redis 也不足以满足对数据读写的速度要求。</p>
</blockquote>
<h3 id="Redis-基本使用"><a href="#Redis-基本使用" class="headerlink" title="Redis 基本使用"></a>Redis 基本使用</h3><h4 id="安装指令"><a href="#安装指令" class="headerlink" title="安装指令"></a>安装指令</h4><p>在控制台输入以下指令安装和使用 Redis：</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">sudo apt-get install redis-server       <span class="comment"># 安装 Redis 数据库（仅限 Ubuntu 可用）</span></span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">redis-server                            <span class="comment"># 启动 Redis 数据库</span></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">redis-server --port 6380                <span class="comment"># 启动 Redis 数据库，在指定端口</span></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">redis-server redis-6379.conf            <span class="comment"># 启动 Redis 数据库，使用指定配置文件</span></span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">redis-cli                               <span class="comment"># 进入 Redis 控制台，在默认端口</span></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">redis-cli -p 6380                       <span class="comment"># 进入 Redis 控制台，在指定端口</span></span></span><br></pre></td></tr></table></figure>

<h4 id="基础配置"><a href="#基础配置" class="headerlink" title="基础配置"></a>基础配置</h4><p>在 Redis 安装目录下的 <code>redis.conf</code> 文件是 Redis 默认配置文件，启动 Redis 数据库时默认加载。</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">daemonize no                     # 守护线程，打开后启动 Redis 控制台不提示</span><br><span class="line">bind 127.0.0.1                   # 绑定 IP 地址，绑定后只能通过该地址访问 Redis</span><br><span class="line">port 6379                        # 端口号</span><br><span class="line">databases 16                     # 存储区域数量</span><br></pre></td></tr></table></figure>

<h4 id="日志配置"><a href="#日志配置" class="headerlink" title="日志配置"></a>日志配置</h4><p>Redis 总共支持四个日志级别：debug / verbose / notice / warning ，从前往后日志记录信息逐渐减少。通常情况下开发环境设为 verbose ，生产环境设为 notice 。</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">loglevel verbose                 # 日志级别</span><br><span class="line">logfile 6379.log                 # 日志文件名</span><br></pre></td></tr></table></figure>


<h4 id="持久化配置"><a href="#持久化配置" class="headerlink" title="持久化配置"></a>持久化配置</h4><p>默认使用 RDB 方式持久化数据，相关配置如下：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">save 900 1                            # 自动同步数据条件，900s 内变更 1 个 key 值则持久化</span><br><span class="line">save 300 10                           # 自动同步数据条件，300s 内变更 10 个 key 值则持久化</span><br><span class="line"></span><br><span class="line">rdbcompression yes                    # 是否压缩数据，压缩后节省空间但读取较慢</span><br><span class="line">rdbchecksum yes                       # 是否格式校验（默认开启），校验降低文件损坏风险但读取较慢</span><br><span class="line"></span><br><span class="line">dbfilename dump.rdb                   # 保存文件名</span><br><span class="line">dir ./                                # 保存文件位置</span><br></pre></td></tr></table></figure>

<p>可以在配置文件中改用 AOF 方式持久化数据，刷新文件条件有三种类型： always / everysec / no 。</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">appendonly yes                        # 选用 AOF 方式持久化</span><br><span class="line">appendsync everysec                   # 刷新文件条件，每秒更新一次操作日志</span><br></pre></td></tr></table></figure>

<h4 id="容量配置"><a href="#容量配置" class="headerlink" title="容量配置"></a>容量配置</h4><p>对 Redis 数据库占用空间和客户链接做出限制。</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">maxclients 100                        # 客户连接数上限，超出后拒绝客户访问，为 0 表示不限制</span><br><span class="line">timeout 300                           # 客户闲置时长，超出后关闭连接，为 0 表示不关闭</span><br><span class="line"></span><br><span class="line">maxmemory 50                          # Redis 最大占用内存比例，为 0 表示全部可用</span><br><span class="line">maxmemory-samples                     # Redis 随机选取数据数量</span><br><span class="line">maxmemery-policy volatile-lru         # Redis 逐出策略</span><br></pre></td></tr></table></figure>

<h4 id="多机配置"><a href="#多机配置" class="headerlink" title="多机配置"></a>多机配置</h4><p>如果我们要设置集群，则需要进行以下配置：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">cluster enabled yes                        # 开启集群</span><br><span class="line">cluster-config-file nodes.conf             # 集群配置文件</span><br></pre></td></tr></table></figure>

<p>如果我们要设置主从服务器，则需要进行以下配置：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"># 主服务器</span><br><span class="line">requirepass 123456                    # 主服务器设置密码（可选）</span><br><span class="line">repl-backlog-size 1mb                 # 缓冲区大小</span><br><span class="line"></span><br><span class="line"># 从服务器</span><br><span class="line">slaveof 127.0.0.1 6379                # 主服务器套接字，设置后自动连接</span><br><span class="line">masterauth 123456                     # 主服务器密码</span><br><span class="line">slave-serve-stale-data no             # 同步数据时是否允许读数据</span><br></pre></td></tr></table></figure>


<hr>
<h2 id="Redis-基础"><a href="#Redis-基础" class="headerlink" title="Redis 基础"></a>Redis 基础</h2><ol>
<li><p>在 Redis 中单个指令都是原子性操作，通过指令操作 Redis 数据时无需担心线程安全问题。</p>
</li>
<li><p>Redis 以 key-value 的形式保存数据：key 值一定为 string 类型，而 value 值支持以下五种基础类型：</p>
</li>
</ol>
<table>
<thead>
<tr>
<th>数据类型</th>
<th>存储形式</th>
</tr>
</thead>
<tbody><tr>
<td>string</td>
<td>字符串</td>
</tr>
<tr>
<td>hash</td>
<td>哈希表</td>
</tr>
<tr>
<td>list</td>
<td>链表</td>
</tr>
<tr>
<td>set</td>
<td>哈希集</td>
</tr>
<tr>
<td>sorted_set</td>
<td>二叉树集</td>
</tr>
</tbody></table>
<h3 id="存储区域"><a href="#存储区域" class="headerlink" title="存储区域"></a>存储区域</h3><p>Redis 将数据存储分为多个相互独立的区域，将 Redis 操作局限在自己的存储区域内。通常划分为 16 个（编号 0-15），默认使用编号 0 。 </p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">select 1                         <span class="comment"># 改用 1 号存储区域</span></span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">dbsize                           <span class="comment"># 返回当前区域 key 数量</span></span></span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">move key 2                       <span class="comment"># 将当前 key 迁移到 2 号存储区域</span></span></span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">flushdb                          <span class="comment"># 清空当前存储区域</span></span></span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">flushall                         <span class="comment"># 清空全部存储区域</span></span></span><br></pre></td></tr></table></figure>

<h3 id="key-操作"><a href="#key-操作" class="headerlink" title="key 操作"></a>key 操作</h3><p><strong>基本操作</strong></p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">del key                            <span class="comment"># 删除 key</span></span></span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">exists key                         <span class="comment"># 判断是否存在 key</span></span></span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash"><span class="built_in">type</span> key                           <span class="comment"># 返回 key 对应的 value 类型</span></span>       </span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">rename key newkey                  <span class="comment"># 重命名</span></span></span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">renamenx key newkey                <span class="comment"># 重命名（返回 1），新名称已存在则失败（返回 0）</span></span></span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash"><span class="built_in">sort</span>                               <span class="comment"># 对 key 排序</span></span></span><br></pre></td></tr></table></figure>

<p><strong>时效性控制</strong></p>
<p>Redis 中可以为 key 设置有效期，key 过期后会由 Redis 执行删除策略回收内存空间。</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">expire key 10                      <span class="comment"># key 10s 内有效</span></span></span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">expireat key 1355292000            <span class="comment"># key 截至时间戳有效</span></span></span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">persist key                        <span class="comment"># key 永久有效</span></span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">ttl key                            <span class="comment"># 返回 key 剩余有效时间，若不存在返回 -2 ，永久返回 -1</span></span></span><br></pre></td></tr></table></figure>

<p><strong>查询操作</strong></p>
<p>Redis 支持查询存储区域内含有的 key，且允许使用以下通配符：</p>
<ul>
<li><code>*</code> 表示任意数量字符</li>
<li><code>?</code> 表示任意一个字符</li>
<li><code>[]</code> 表示一个指定字符</li>
</ul>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">keys *                             <span class="comment"># 查询所有 key</span></span></span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">keys user:*                        <span class="comment"># 查询所有 user 的 key</span></span></span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">keys <span class="built_in">id</span>:75??                       <span class="comment"># 查询 ID 为 7500-7599 的 key</span></span></span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">keys <span class="built_in">id</span>:7[2345]55                  <span class="comment"># 查询 ID 为 7255/7355/7455/7555 的 key</span></span></span><br></pre></td></tr></table></figure>

<h3 id="基础类型"><a href="#基础类型" class="headerlink" title="基础类型"></a>基础类型</h3><h4 id="string-类型"><a href="#string-类型" class="headerlink" title="string 类型"></a>string 类型</h4><p>Redis 的 string 类型中，key 值对应的存储空间内将保存一个字符串数据，</p>
<p>key 值标准命名格式为 <code>表名:主键名:主键值:字段名</code>，如 <code>user:id:15942348:name - &quot;王东浩&quot;</code>。</p>
<p><strong>基本操作</strong></p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash"><span class="built_in">set</span> key 10                     <span class="comment"># 设置键值对</span></span></span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">get key                        <span class="comment"># 获取键值，不存在则返回 nil</span></span></span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">del key                        <span class="comment"># 删除键值对</span></span></span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">strlen key                     <span class="comment"># 获取价值的字符串长度</span></span></span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">append key 0                   <span class="comment"># 在键值尾部追加</span></span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">mset key1 10 key2 100          <span class="comment"># 设置多个数据</span></span></span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">mget key1 key2                 <span class="comment"># 获取多个数据</span></span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">setex key 10 1                 <span class="comment"># 设置键值对，10s 后自动删除</span></span></span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">psetex key 10 1                <span class="comment"># 设置键值对，10ms 后自动删除</span></span></span><br></pre></td></tr></table></figure>

<p><strong>数据操作</strong></p>
<p>如果字符串为合法数字，可以当作数字处理。但数值不能超过 shell 中的 long 类型。</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">incr key                       <span class="comment"># 键值加一</span></span></span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">decr key                       <span class="comment"># 键值减一</span></span></span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">incrby key 10                  <span class="comment"># 键值加十</span></span></span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">decrby key 10                  <span class="comment"># 键值减十</span></span></span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">incrbyfloat key -1.5           <span class="comment"># 键值加 -1.5</span></span></span><br></pre></td></tr></table></figure>


<h4 id="hash-类型"><a href="#hash-类型" class="headerlink" title="hash 类型"></a>hash 类型</h4><p>hash 类型中，key 值对应的存储空间内可以保存多个键值对(field-value)：field 和 value 都必须是字符串类型。当键值对较少时存储空间内采用数组存储，当键值对较多时采用哈希存储。</p>
<p>十分适合存储对象，每个键值对记录对象的一个属性。</p>
<p><img src="hash.png" alt="hash"></p>
<p><strong>基本操作</strong></p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">hset key field 10               <span class="comment"># 设置/更新键值对</span></span></span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">hsetnx key field 10             <span class="comment"># 如果键值不存在则设置键值对</span></span></span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">hget key field                  <span class="comment"># 获取键值</span></span></span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">hgetall key                     <span class="comment"># 获取全部键值</span></span></span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">hdel key field                  <span class="comment"># 删除键值对</span></span></span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">hlen key                        <span class="comment"># 获取键值对数量</span></span></span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">hexists key field               <span class="comment"># 判断是否存在字段（返回 1 或 0）</span></span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">hmset key field1 1 field2 2     <span class="comment"># 设置/修改多个键值对</span></span></span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">hmget key field1 field2         <span class="comment"># 获取多个键值对</span></span></span><br></pre></td></tr></table></figure>

<p><strong>扩展操作</strong></p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">hkeys key                       <span class="comment"># 返回 key 对应的所有 field</span></span></span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">hvals key                       <span class="comment"># 返回 key 对应的所有 value</span></span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">hincrby key field 1             <span class="comment"># 键值加一</span></span></span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">hdecrby key field 1             <span class="comment"># 键值减一</span></span></span><br></pre></td></tr></table></figure>

<h4 id="list-类型"><a href="#list-类型" class="headerlink" title="list 类型"></a>list 类型</h4><p>list 类型中，key 值对应的存储空间内可以保存多个字符串数据，采用双向链表实现。具有索引的概念，但还是更适合从链表两侧操作。字符串总容量不能超过 2 的 32 次方。</p>
<p>十分适合存储有序信息，比如粉丝列表。</p>
<p><strong>基本操作</strong></p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">lpush list 1                    # 链表左侧插入数据，返回下标</span><br><span class="line">rpush list 2                    # 链表右侧插入数据，返回下标</span><br><span class="line">lpop list                       # 获取并删除最左侧数据</span><br><span class="line">rpop list                       # 获取并删除最右侧数据</span><br><span class="line">blpop list 10                   # 获取并删除最左侧数据，不存在则至多等待 10 s</span><br><span class="line">lrem list 3 x                   # 从左侧开始，删除三个为 x 的数据</span><br><span class="line"></span><br><span class="line">lrange list 0 2                 # 返回左侧前3个数据</span><br><span class="line">lrange list 0 -1                # 返回全部数据（常用）</span><br><span class="line">lindex list 0                   # 返回指定位置数据</span><br><span class="line">llen list                       # 返回字符串个数</span><br></pre></td></tr></table></figure>

<h4 id="set-类型"><a href="#set-类型" class="headerlink" title="set 类型"></a>set 类型</h4><p>set 类型中，key 值对应的存储空间内可以保存多个字符串数据，采用哈希存储实现。随机查询效率比 list 类型更高。字符串总容量不能超过 2 的 32 次方。</p>
<p>十分适合存储集合类信息，比如用户感兴趣的话题、用户权限。</p>
<p><strong>基本操作</strong></p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">sadd set member                  # 添加数据（可以是多个）</span><br><span class="line">srem set member                  # 删除数据（可以是多个）</span><br><span class="line">smembers set                     # 展示全部数据  </span><br><span class="line"></span><br><span class="line">scard set                        # 返回数据个数</span><br><span class="line">sismember set                    # 判断是否含有数据</span><br><span class="line"></span><br><span class="line">srandmember set 5                # 随机从集合中选取 5 个数据</span><br><span class="line">spop set                         # 返回并删除一个随机数据</span><br></pre></td></tr></table></figure>

<p><strong>扩展操作</strong></p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">sinter set1 set2                   # 交</span><br><span class="line">sunion set1 set2                   # 并</span><br><span class="line">sdiff set1 set2                    # 差</span><br><span class="line">sinterstore newset set1 set2       # 交且存入新集合</span><br><span class="line">sunionstore newset set1 set2       # 并且存入新集合</span><br><span class="line">sdiffstore newset set1 set2        # 差且存入新集合</span><br><span class="line"></span><br><span class="line">smove oldset newset 5              # 数据从旧集合迁移到新集合</span><br></pre></td></tr></table></figure>

<h4 id="sorted-set-类型"><a href="#sorted-set-类型" class="headerlink" title="sorted_set 类型"></a>sorted_set 类型</h4><p>如果我们需要数据查询效率较高且有序，则可以使用 sorted_set 类型。底层和 set 结构相同采用哈希存储（value 值仍不可重复），但在 key-value 存储结构后添加 score 属性为数据排序，默认从小到大。score 是数字且可以使用小数，但如果使用小数浮点类型可能会出现精度丢失。</p>
<p>可以用来存储排行榜等有序数据集合，还可以用于存储时效性或者带有权重的任务队列，用当前时间或者权重作为 score 。</p>
<p><strong>基本操作</strong></p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">zadd set score1 member             # 添加数据且标记序号（可以是多个）</span><br></pre></td></tr></table></figure>

<h3 id="高级类型"><a href="#高级类型" class="headerlink" title="高级类型"></a>高级类型</h3><p>此外，Redis 还提供了 Bitmaps、 HyberLogLog、GEO 三种高级数据类型，用来适配特定的应用场景。</p>
<h4 id="Bitmaps-类型"><a href="#Bitmaps-类型" class="headerlink" title="Bitmaps 类型"></a>Bitmaps 类型</h4><p>Bitmaps 类型中用作存储布尔值：每个 key 对应若干字节数据（字节数 = 最大编号 / 8），每字节可以存储 8 个 boolean 值。</p>
<p>如果 Redis 要存储大量 boolean 值，使用 Bitmaps 类型可以显著节省内存空间。</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">setbit bits 0 1                  # 将 0 位置为 1(true)</span><br><span class="line">getbit bits 0                    # 取 0 位的值</span><br></pre></td></tr></table></figure>

<h4 id="HyperLogLog-类型"><a href="#HyperLogLog-类型" class="headerlink" title="HyperLogLog 类型"></a>HyperLogLog 类型</h4><p>HyperLogLog 类型用作数据统计，只记录数量不保存数据，且当数据量巨大时存在误差！</p>
<p>使用 HyperLogLog 类型可以显著节省内存空间，每个 key 仅占用 12k 内存标记基数。</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">setbit bits 0 1                  # 将 0 位置为 1(true)</span><br><span class="line">getbit bits 0                    # 取 0 位的值</span><br></pre></td></tr></table></figure>

<h4 id="GEO-类型"><a href="#GEO-类型" class="headerlink" title="GEO 类型"></a>GEO 类型</h4><p>GEO 类型用作地理位置计算，根据经纬度。</p>
<hr>
<h2 id="Redis-高级"><a href="#Redis-高级" class="headerlink" title="Redis 高级"></a>Redis 高级</h2><h3 id="持久化"><a href="#持久化" class="headerlink" title="持久化"></a>持久化</h3><p>Redis 使用内存存储，一旦断电可能会导致数据丢失。因此需要将数据保存到永久性存储介质中，防止数据意外丢失。</p>
<p><em>如果 Redis 负责为数据库高热度数据访问加速或者一些其他业务（数据库中有重复数据），那么没必要为 Redis 数据持久化。</em></p>
<p>Redis 持久化有以下两种方式：</p>
<h4 id="数据快照-RDB"><a href="#数据快照-RDB" class="headerlink" title="数据快照 RDB"></a>数据快照 RDB</h4><p><strong>定时将全部数据存入文件</strong>。存储速度慢但是恢复数据的速度很快，如果保存不及时仍会丢失少量数据。</p>
<p>数据以二进制形式默认存储在 <code>安装目录/data/dump.rgb</code> 文件。如果 Redis 数据库被关闭，下次重启时会从该文件读取数据。</p>
<p><strong>手动存储</strong></p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">save                      # 数据存入文件（会阻塞 Redis 数据库，导致其他指令无法执行）</span><br><span class="line">bgsave                    # 数据存入文件（Redis 数据库调创建单独进程完成指令）</span><br><span class="line"></span><br><span class="line">debug reload              # 重启 Redis，且关闭时将数据存入文件</span><br><span class="line">shutrown save             # 关闭 Redis，且关闭时将数据存入文件</span><br></pre></td></tr></table></figure>

<p><strong>修改配置</strong></p>
<p>在 <code>安装目录/conf/redis-6379.conf</code> 配置文件内可以修改默认配置：</p>
<ul>
<li>如果操作系统内安装了多个 Redis 数据库（使用不同的端口），必须通过修改存储文件名加以区分。</li>
</ul>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">dir data2                            # 修改存储路径（默认 data） </span><br><span class="line">dbfilename dump-6379.rgb             # 修改存储文件名（默认 dump.rgb）</span><br><span class="line">rdbcompression no                    # 关闭数据压缩（默认开启），读取文件加快但文件会变大</span><br><span class="line">rdbchecksum no                       # 关闭格式校验（默认开启），读取文件加快但存在文件损坏风险</span><br><span class="line">stop-writes-on-bgsave-error no       # 后台存储出现错误不停止（默认停止）</span><br></pre></td></tr></table></figure>

<ul>
<li>通过修改配置文件，可以让 Redis 数据库可以自动调用 bgsave 指令更新 RDB 文件。</li>
</ul>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">save 100 10                          # 自动存储（100s 内发生 10 个 key 数据变化时触发）</span><br></pre></td></tr></table></figure>


<h4 id="日志记录-AOF"><a href="#日志记录-AOF" class="headerlink" title="日志记录 AOF"></a>日志记录 AOF</h4><p><strong>将对数据的操作过程存入文件</strong>。这种方式刷新更频繁因此丢失数据概率更低，但恢复数据的速度比 RDB 方式更慢，占用存储空间也更大。</p>
<p>数据以二进制形式默认存储在 <code>安装目录/data/appendonly.aof</code> 文件。如果 Redis 数据库被关闭，下次重启时会根据该文件恢复数据。</p>
<p><strong>文件重写</strong></p>
<p>随着命令不断写入 AOF ，AOF 文件会越来越大，占用内存增多、恢复数据也会变慢。因此 Redis 需要对 AOF 文件进行重写，合并指令记录。</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">rewriteaof                          # 重写 AOF 文件（会阻塞 Redis 数据库，导致其他指令无法执行）</span><br><span class="line">bgrewriteaof                        # 重写 AOF 文件（Redis 数据库调创建单独进程完成指令）                        </span><br></pre></td></tr></table></figure>


<p><strong>修改配置</strong></p>
<p>AOF 不是默认持久化方式，需要在 <code>安装目录/conf/redis-6379.conf</code> 配置文件内修改默认配置：</p>
<ul>
<li>必须通过配置文件开启并配置 AOF 存储。</li>
</ul>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">appendonly yes                        # 选用 AOF 方式持久化</span><br><span class="line"></span><br><span class="line">appendsync always                     # 每次操作刷新文件：非常频繁，损耗性能</span><br><span class="line">appendsync everysec                   # 每秒刷新文件（默认）</span><br><span class="line">appendsync no                         # 手动刷新文件</span><br></pre></td></tr></table></figure>

<ul>
<li>修改路径和文件名的操作和 RDB 方法类似。</li>
</ul>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">dir data2                             # 修改存储路径（默认 data） </span><br><span class="line">dbfilename appendonly-6379.aof        # 修改存储文件名（默认 appendonly.aof）</span><br></pre></td></tr></table></figure>

<ul>
<li>通过修改配置文件，可以让 Redis 数据库自动调用 bgrewriteaof 指令重写 AOF 文件。</li>
</ul>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">略，之后补充</span><br></pre></td></tr></table></figure>

<h3 id="事务"><a href="#事务" class="headerlink" title="事务"></a>事务</h3><p>假如我们通过多个操作执行一次购物，如果在这个过程中还执行了其他操作，可能导致我们的购物过程出现意想不到的错误。</p>
<p>因此我们引入事务的概念，将多个操作看作一个不可分割的整体，统一执行而不会被其他操作打断。</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">multi                     # 开启事务，之后的命令不再立刻执行、而是进入任务队列</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">输入事务内的命令</span></span><br><span class="line"></span><br><span class="line">exec                      # 执行事务，执行任务队列里的命令</span><br><span class="line">discard                   # 取消事务，清空任务队列</span><br></pre></td></tr></table></figure>

<ol>
<li>如果事务中包含语法错误（不能识别的命令），所有的命令都不会执行。</li>
<li>如果事务中包含无法执行的命令，仅有出错的命令将不会被执行，其他被执行的命令需要开发者自行回滚。</li>
</ol>
<h4 id="锁"><a href="#锁" class="headerlink" title="锁"></a>锁</h4><p>在事务准备的过程中，如果执行的其他操作导致触发事务的条件发生了变化，这个时候就不应该继续执行事务。</p>
<p>我们引入了锁的概念来监视特定 key，在执行事务前如果其 value 发生了变化则终止事务执行。</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">watch key1 key2            # 监视 key，书写在 multi 命令前</span><br><span class="line">unwatch                    # 取消监视 key，书写在 multi 命令前</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">在之后执行事务</span></span><br></pre></td></tr></table></figure>

<h4 id="分布式锁"><a href="#分布式锁" class="headerlink" title="分布式锁"></a>分布式锁</h4><p>如果 key 值变化极为频繁，那么使用普通锁会导致事务一直被终止。我们引入了分布式锁的概念，在加锁期间不允许其他进程对该值修改。</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">setnx lock-num 1               # 对 key(num) 加公共锁，其他线程不能对其进行操作。成功则返回 1，若已有锁导致失败返回 0</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">输入命令或者事务</span></span><br><span class="line"></span><br><span class="line">del lock-num                   # 对 key(num) 解公共锁</span><br></pre></td></tr></table></figure>

<p>分布式锁如果长期不被释放，就会出现死锁，导致其他操作无法继续执行。我们可以对分布式锁计时。<strong>计时分布式锁常用于多部署平台统一竞争锁。</strong></p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">expire lock-num 10                # 对 key(num) 加公共锁，10s 后自动释放</span><br><span class="line">pexpire lock-num 10               # 对 key(num) 加公共锁，10ms 后自动释放</span><br></pre></td></tr></table></figure>

<h3 id="删除策略"><a href="#删除策略" class="headerlink" title="删除策略"></a>删除策略</h3><p>Redis 中每个存储区域除了存储 key-value 值，还会开辟额外的存储空间 expires 记录每个 key-value 的存储地址以及过期时间。如果 key 过期或被删除指令删除，那么 Redis 要执行删除策略清理内存空间。</p>
<p>Redis 删除策略有以下三种方式，主要使用惰性删除和定期删除两种方式。</p>
<ol>
<li><strong>定时删除</strong></li>
</ol>
<p>key 过期后，存储 key-value 的内存地址立即被清空。</p>
<p>节省内存资源，但可能抢占处在繁忙状态的 CPU。</p>
<ol start="2">
<li><strong>惰性删除</strong></li>
</ol>
<p>key 过期后不做任何处理。访问 key 时才检查是否过期，如果过期存储该 key-value 的内存地址才被清空。</p>
<p>节省 CPU 资源，但过期键值对可能大量占用内存。</p>
<ol start="3">
<li><strong>定期删除</strong></li>
</ol>
<p>对于 16 个存储区域的 expires 进行轮询，对选中的 expires 随机选择 W 个 key 进行检查，如果 key 过期就进行删除。</p>
<ul>
<li>如果过期 key 超过 25%，那么重复检查该 expires 存储区域。</li>
<li>如果过期 key 少于 25%，那么按顺序检查下一个 expires 存储区域。</li>
</ul>
<h3 id="逐出策略"><a href="#逐出策略" class="headerlink" title="逐出策略"></a>逐出策略</h3><p>如果 Redis 使用内存空间前会检查内存容量。如果已被占满，那么 Redis 要执行逐出策略删除部分数据，以清理内存空间执行指令。</p>
<p>在选取删除数据时 Redis 并不会扫描全库数据，而是随机选取部分数据检测并从中删除：以节省 CPU 性能。</p>
<p>响应配置如下：</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">maxmemory 50                            # Redis 最大占用内存比例，默认为 0（全部可用）</span><br><span class="line">maxmemory-samples                       # Redis 随机选取数据数量</span><br><span class="line">maxmemery-policy volatile-lru           # Redis 逐出策略</span><br></pre></td></tr></table></figure>

<p>Redis 逐出策略有以下三种方式，在配置文件中配置即可。</p>
<ol>
<li><strong>检查会过期数据</strong></li>
</ol>
<ul>
<li><code>volatile-lru</code> ：（推荐）挑选最久未使用的数据淘汰。</li>
<li><code>volatile-lfu</code> ：挑选最近一段时间使用频率最低的数据淘汰。</li>
<li><code>volatile-ttl</code> ：挑选将要过期的数据淘汰。</li>
<li><code>volatile-random</code> ：随机挑选数据淘汰。</li>
</ul>
<ol start="2">
<li><strong>检查全部数据</strong></li>
</ol>
<ul>
<li><code>allkeys-lru</code> ：挑选最久未使用的数据淘汰。</li>
<li><code>allkeys-lfu</code> ：挑选最近一段时间使用频率最低的数据淘汰。</li>
<li><code>allkeys-random</code> ：随机挑选数据淘汰。</li>
</ul>
<ol start="3">
<li><strong>不逐出数据</strong></li>
</ol>
<ul>
<li><code>no-enviction</code> ：（默认）抛出错误 Out Of Memery。</li>
</ul>
<hr>
<h2 id="Redis-多机"><a href="#Redis-多机" class="headerlink" title="Redis 多机"></a>Redis 多机</h2><h3 id="集群"><a href="#集群" class="headerlink" title="集群"></a>集群</h3><p>当数据量过大时，单个 Redis 数据库就无法存放。我们需要多个 Redis 数据库组成集群(cluster)，去分别<strong>存放不同的数据</strong>。</p>
<ol>
<li><p>key-value 数据进行存入时，会根据 key 的哈希值对 16384 取模，放入相应的槽(slot)存放。这 16384 个槽会分发给各个存储空间。</p>
</li>
<li><p>各个存储空间之间会相互通信，并记录所有编号的槽都存储在哪个存储空间：保证最多访问 Redis 两次可以命中。</p>
</li>
</ol>
<p><strong>配置文件</strong></p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">cluster enabled yes                            # 开启集群</span><br><span class="line">cluster-config-file nodes-6379.conf            # 集群配置文件（默认为 nodes.conf）</span><br></pre></td></tr></table></figure>

<p><strong>集群指令</strong></p>
<ul>
<li><p>通过 <code>redis-cli</code> 打开 Redis 集群中的数据库，输入指令去插入不属于这个存储空间的键值，会返回错误。</p>
</li>
<li><p>通过 <code>redis-cli -c</code> 打开 Redis 集群中的数据库，会自动将插入数据指令转发到相应的存储空间。</p>
</li>
</ul>
<h3 id="主从复制"><a href="#主从复制" class="headerlink" title="主从复制"></a>主从复制</h3><p>如果数据只交给一个 Redis 服务器处理，那么可能面临两大问题：</p>
<ol>
<li>服务器同时处理过多读写操作，超过服务器负载。</li>
<li>一旦服务器宕机，就会导致服务异常中断。</li>
</ol>
<p>为了避免这两个问题，我们必须引入多个 Redis 服务器来保存相同数据，并采用主从复制结构：一个主服务器 Master 对应多个从服务器 Slave 。</p>
<ol>
<li><strong>读写分离</strong>：Master 负责写入数据；Slave 则会自动同步数据，并负责读取数据。起到均衡负载的作用。</li>
<li><strong>数据冗余</strong>：即使某个 Slave 故障，由于其他 Slave 已保存了这些数据，并不会导致服务中断。 </li>
</ol>
<h4 id="容错机制"><a href="#容错机制" class="headerlink" title="容错机制"></a>容错机制</h4><ul>
<li><p>主从之间使用<strong>心跳</strong>建立连接。 Slave 每秒 ping 一次，汇报自己的偏移量、获取最新的指令。Master 默认每 10s ping 一次 Slave， 检查 slave 是否在线：如果 Slave 多数掉线或者高延迟，Master 停止写和数据同步功能，保障数据稳定性。</p>
</li>
<li><p>在分布式系统里还会部署多个 Redis 服务器作为<strong>哨兵</strong>（除端口号外完全相同），不提供数据服务，只负责监控主从机制的运行：如果发现 Master 宕机，哨兵将通知所有机器，使 Master 下线并开启投票机制选用一个 Slave 担任 Master 。</p>
</li>
</ul>
<h4 id="配置连接"><a href="#配置连接" class="headerlink" title="配置连接"></a>配置连接</h4><p>Master 和 Slave 通过以下过程建立连接。</p>
<p><img src="/pic/%E5%BB%BA%E7%AB%8B%E8%BF%9E%E6%8E%A5.png" alt="x"></p>
<p>相比于输入指令，我们一般直接修改 conf 文件夹内的配置文件，由机器自动建立连接。</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">Master</span> </span><br><span class="line">requirepass 123456                # 连接主服务器需要密码（可选）</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">Slave</span></span><br><span class="line">slaveof 127.0.0.1 6379            # 根据套接字自动连接主服务器</span><br><span class="line">masterauth 123456                 # 主服务器密码</span><br></pre></td></tr></table></figure>


<h4 id="数据同步"><a href="#数据同步" class="headerlink" title="数据同步"></a>数据同步</h4><p>Slave 会定期复制 Master 的持久化文件，以保证数据同步。</p>
<p><img src="/pic/%E5%90%8C%E6%AD%A5%E6%95%B0%E6%8D%AE.png" alt="x"></p>
<p><strong>复制缓冲区</strong>：一个先入先出队列，用来存储 AOF 形式指令。由偏移量记录当前执行到的位置。</p>
<ul>
<li>如果开启 AOF ，在创建时就会开启复制缓冲区。（偏移量记录自己执行到的位置）</li>
<li>如果使用 RDB ，在成为 Master 时会开启复制缓冲区。（Master 可以含有多个偏移量，记录不同 Slave 读取到的位置。）</li>
</ul>
<p><em>如果 Slave 过多，数据同步也会导致 Master 负载过高。因此 Slave 也可以兼职 Master，向下级 Slave 提供服务。但层次太多会导致数据延迟，慎用。</em></p>
<ol>
<li>数据同步阶段应避免流量高峰期，防止影响业务正常执行；也不要多个从服务器同时同步数据。</li>
<li>缓冲区应该足够大，否则一旦发生数据溢出会反复进行全量复制。</li>
</ol>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">Master</span></span><br><span class="line">repl-backlog-size 2mb                 # 修改缓冲区大小（默认 1mb）</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">Slave</span></span><br><span class="line">slave-serve-stale-data no             # 不允许同步数据时读数据</span><br></pre></td></tr></table></figure>

<h4 id="命令传播"><a href="#命令传播" class="headerlink" title="命令传播"></a>命令传播</h4><p>服务器每次启动都会随机生成一个运行 ID（40 位 16 进制字符） ，Master 和 Slave 之间正是依靠 Master 的运行 ID 相互识别。</p>
<p><img src="/pic/%E5%91%BD%E4%BB%A4%E4%BC%A0%E6%92%AD.png" alt="x"></p>
<hr>
<h2 id="Redis-潜在问题"><a href="#Redis-潜在问题" class="headerlink" title="Redis 潜在问题"></a>Redis 潜在问题</h2><h3 id="缓存故障"><a href="#缓存故障" class="headerlink" title="缓存故障"></a>缓存故障</h3><p>Redis 缓存技术常用于高并发情况下，有效减轻服务器和数据库负载。如果 Redis 出现问题导致无法均衡负载，就可能导致服务崩溃。</p>
<ol>
<li><strong>缓存预热</strong></li>
</ol>
<p>当系统刚启动时，<strong>由于 Redis 尚未保存数据导致无法命中</strong>，数据库被频繁请求数据，由于过载导致数据库崩溃。</p>
<p>数据库崩溃后， Redis 和应用服务器无法获取数据，请求积压会进一步导致 Redis 和服务器崩溃。</p>
<ol start="2">
<li><strong>缓存雪崩</strong></li>
</ol>
<p>当流量激增时，<strong>如果 Redis 大量 key 过期导致无法命中</strong>，数据库被频繁请求数据，由于过载导致数据库崩溃。</p>
<p>数据库崩溃后， Redis 和应用服务器无法获取数据，请求积压会进一步导致 Redis 和服务器崩溃。</p>
<ol start="3">
<li><strong>缓存击穿</strong></li>
</ol>
<p>当流量激增时，<strong>如果 Redis 某个极高热度的 key 过期导致无法命中</strong>，数据库被频繁请求数据，由于过载导致数据库崩溃。</p>
<p>数据库崩溃后， Redis 和应用服务器无法获取数据，请求积压会进一步导致 Redis 和服务器崩溃。</p>
<ol start="4">
<li><strong>缓存穿透</strong></li>
</ol>
<p>当流量激增时，<strong>如果 Redis 收到大量非法访问导致无法命中</strong>，数据库被频繁请求数据，由于过载导致数据库崩溃。</p>
<p>数据库崩溃后， Redis 和应用服务器无法获取数据，请求积压会进一步导致 Redis 和服务器崩溃。</p>
<h3 id="一致性问题"><a href="#一致性问题" class="headerlink" title="一致性问题"></a>一致性问题</h3><p>如果在缓存中存储数据库数据备份，以提高查询效率，就一定会出现一致性问题，导致脏读。比如数据库中数据从 1 更新到 10 ，但缓存还未更新时读取，就会读取到 1。这个问题难以避免。 </p>
<ol>
<li>缓存就是缓存，必须要设过期时间。</li>
<li>实时性要求比较高的（比如充值），直接读数据库。</li>
<li>数据库并发高需要分库分表。</li>
</ol>
<hr>
<h2 id="Redis-客户端"><a href="#Redis-客户端" class="headerlink" title="Redis 客户端"></a>Redis 客户端</h2><p>我们在实际使用 Redis 时往往要通过 Redis 客户端，以便在程序中直接操作 Redis 。常使用的 Redis 客户端有 Jedis、 以及功能更为高级的 Redisson、Lettuce 等。</p>
<h3 id="RedisTemplate-类"><a href="#RedisTemplate-类" class="headerlink" title="RedisTemplate 类"></a>RedisTemplate 类</h3><p>Spring Boot 提供了 RedisTemplate 工具类直接对 Redis 进行操作，也提供了 StringRedisTemplate 类继承 RedisTemplate 类，两者方法完全一致。</p>
<ul>
<li><code>RedisTemplate</code> 类：存储数据时序列化成字节数组保存，在 Redis 中数据为字节码。读取数据时自动转化为对象。</li>
<li><code>StringRedisTemplate</code> 类：存储数据直接以字符串形式保存，在 Redis 中数据直接可读。只适用于字符串类型的数据。</li>
</ul>
<p>由于两种序列化方法不同导致的数据存储形式差异，两个类之间不能对另一方存储的 Redis 数据进行操作。</p>
<p><strong>常用方法</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* 直接对 key 操作 */</span></span><br><span class="line">redisTemplate.delete(<span class="string">&quot;key&quot;</span>);                                             <span class="comment">// 删除 key</span></span><br><span class="line">redisTemplate.delete(collection);                                        <span class="comment">// 批量删除 key</span></span><br><span class="line">redisTemplate.expire(<span class="string">&quot;key&quot;</span>,<span class="number">10</span>,TimeUnit.MINUTES);                         <span class="comment">// 设置 key 失效时间</span></span><br><span class="line"><span class="type">Long</span> <span class="variable">expire</span> <span class="operator">=</span> redisTemplate.getExpire(<span class="string">&quot;key&quot;</span>);                            <span class="comment">// 获取 key 失效时间</span></span><br><span class="line"><span class="type">boolean</span> <span class="variable">flag</span> <span class="operator">=</span> redisTemplate.hasKey(<span class="string">&quot;key&quot;</span>);                              <span class="comment">// 判断 key 是否存在</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 操作字符串 */</span></span><br><span class="line">redisTemplate.opsForValue().set(<span class="string">&quot;key&quot;</span>, <span class="string">&quot;value&quot;</span>);                         <span class="comment">// 设置键值对 </span></span><br><span class="line"><span class="type">String</span> <span class="variable">str</span> <span class="operator">=</span> (String)redisTemplate.opsForValue().get(<span class="string">&quot;key&quot;</span>);             <span class="comment">// 获取键值</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 操作 hash */</span></span><br><span class="line">redisTemplate.opsForHash().put(<span class="string">&quot;HashKey&quot;</span>, <span class="string">&quot;SmallKey&quot;</span>, <span class="string">&quot;HashValue&quot;</span>);                  <span class="comment">// 设置键值对</span></span><br><span class="line">redisTemplate.boundHashOps(<span class="string">&quot;HashKey&quot;</span>).putAll(hashMap);                               <span class="comment">// 批量设置键值对</span></span><br><span class="line"><span class="type">String</span> <span class="variable">value</span> <span class="operator">=</span> (String) redisTemplate.opsForHash().get(<span class="string">&quot;HashKey&quot;</span>, <span class="string">&quot;SmallKey&quot;</span>);       <span class="comment">// 获取键值</span></span><br><span class="line"><span class="type">Map</span> <span class="variable">entries</span> <span class="operator">=</span> redisTemplate.opsForHash().entries(<span class="string">&quot;HashKey&quot;</span>);                         <span class="comment">// 获取全部键值对</span></span><br><span class="line">redisTemplate.boundHashOps(<span class="string">&quot;HashKey&quot;</span>).delete(<span class="string">&quot;SmallKey&quot;</span>);                            <span class="comment">// 删除键值对</span></span><br><span class="line"><span class="type">Boolean</span> <span class="variable">isEmpty</span> <span class="operator">=</span> redisTemplate.boundHashOps(<span class="string">&quot;HashKey&quot;</span>).hasKey(<span class="string">&quot;SmallKey&quot;</span>);          <span class="comment">// 是否含有键值对</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">redisTemplate.opsForList();　　 <span class="comment">// 操作 list</span></span><br><span class="line">redisTemplate.opsForSet();　　  <span class="comment">// 操作 set</span></span><br><span class="line">redisTemplate.opsForZSet();　 　<span class="comment">// 操作有序 set</span></span><br></pre></td></tr></table></figure>




<h3 id="Jedis-客户端"><a href="#Jedis-客户端" class="headerlink" title="Jedis 客户端"></a>Jedis 客户端</h3><p>Jedis 基于 Java 实现，是 shell 程序连接 Redis 数据库最常使用的工具。提供了比较全面的 Redis 命令的支持。</p>
<ul>
<li>Jedis 使用阻塞 I/O，且其方法调用都是同步的，程序流需要等到 sockets 处理完 I/O 才能执行。</li>
<li>Jedis 采取直连模式，在多个线程间共享一个 Jedis 实例线程不安全，多线程操作 Redis 必须要使用多个 Jedis 实例。</li>
</ul>
<ol>
<li><strong>导入依赖</strong></li>
</ol>
<p>Spring Boot 2.x 版本 Redis 默认导入了 lettuce，需要排除才能使用 Redis .</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!-- Redis --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-data-redis<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">exclusions</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">exclusion</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>io.lettuce<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>lettuce-core<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">exclusion</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">exclusions</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"><span class="comment">&lt;!-- Jedis --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>redis.clients<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>jedis<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure>


<ol start="2">
<li><strong>基本使用</strong></li>
</ol>
<p>使用引入的 Jedis 类即可连接 Redis 数据库并进行操作。操作名取自 Redis 指令，如果出现问题则会抛出 JedisDataException。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> redis.clients.jedis.Jedis;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">JedisTest</span>&#123;</span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">jedisTest</span> <span class="params">()</span>&#123;</span><br><span class="line">        <span class="comment">// 连接 Redis</span></span><br><span class="line">        <span class="type">Jedis</span> <span class="variable">jedis</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Jedis</span>(<span class="string">&quot;127.0.0.1&quot;</span>, <span class="number">6379</span>);    </span><br><span class="line">        <span class="comment">// 对 Redis 操作（直接使用 Redis 指令）</span></span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            jedis.set(<span class="string">&quot;name&quot;</span>, <span class="string">&quot;MrJoker&quot;</span>);                  </span><br><span class="line">            System.out.print(jedis.get(<span class="string">&quot;name&quot;</span>));  </span><br><span class="line">        &#125; <span class="keyword">catch</span>(JedisDataException e) &#123;</span><br><span class="line">            System.out.print(<span class="string">&quot;error&quot;</span>);  </span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            <span class="comment">// 关闭 Redis 连接</span></span><br><span class="line">            jedis.close();    </span><br><span class="line">        &#125;                                          </span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在实际开发中，创建多个 Redis 连接会非常复杂且难以管理，Jedis 提供了 JedisPool 类作为 Redis 连接池来管理 Redis 连接。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> redis.clients.jedis.JedisPool;</span><br><span class="line"><span class="keyword">import</span> redis.clients.jedis.JedisPoolConfig;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">JedisTest</span>&#123;</span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">jedisTest</span> <span class="params">()</span>&#123;</span><br><span class="line">        <span class="comment">// 配置连接池</span></span><br><span class="line">        <span class="type">JedisPoolConfig</span> <span class="variable">poolConfig</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">JedisPoolConfig</span>();</span><br><span class="line">        poolConfig.setMaxIdle(<span class="number">50</span>);                 <span class="comment">// 最大空闲数</span></span><br><span class="line">        poolConfig.setMaxTotal(<span class="number">100</span>);               <span class="comment">// 最大连接数</span></span><br><span class="line">        poolConfig.setMaxWaitMillis(<span class="number">20000</span>);        <span class="comment">// 最大等待毫秒数   </span></span><br><span class="line">        <span class="comment">// 创建连接池</span></span><br><span class="line">        <span class="type">JedisPool</span> <span class="variable">pool</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">JedisPool</span>(poolConfig, <span class="string">&quot;localhost&quot;</span>);</span><br><span class="line">        <span class="comment">// 从连接池中获取单个连接</span></span><br><span class="line">        <span class="type">Jedis</span> <span class="variable">jedis</span> <span class="operator">=</span> pool.getResource();</span><br><span class="line">        <span class="comment">// 如果需要密码</span></span><br><span class="line">        <span class="comment">//jedis.auth(&quot;password&quot;);                                 </span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<ol start="3">
<li><strong>Spring Boot 集成</strong></li>
</ol>
<p>Spring Boot 中，我们无需自行创建 Redis 连接，只需要在配置文件中配置好参数。</p>
<figure class="highlight properties"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># REDIS配置</span></span><br><span class="line"><span class="comment"># Redis数据库索引（默认为0）</span></span><br><span class="line"><span class="attr">spring.redis.database</span>=<span class="string">0</span></span><br><span class="line"><span class="comment"># Redis服务器地址</span></span><br><span class="line"><span class="attr">spring.redis.host</span>=<span class="string">localhost</span></span><br><span class="line"><span class="comment"># Redis服务器连接端口</span></span><br><span class="line"><span class="attr">spring.redis.port</span>=<span class="string">6379</span></span><br><span class="line"><span class="comment"># Redis服务器连接密码（默认为空）</span></span><br><span class="line"><span class="attr">spring.redis.password</span>=<span class="string"></span></span><br><span class="line"><span class="comment"># 连接池最大连接数（使用负值表示没有限制）</span></span><br><span class="line"><span class="attr">spring.redis.pool.max-active</span>=<span class="string">8</span></span><br><span class="line"><span class="comment"># 连接池最大阻塞等待时间（使用负值表示没有限制）</span></span><br><span class="line"><span class="attr">spring.redis.pool.max-wait</span>=<span class="string">-1</span></span><br><span class="line"><span class="comment"># 连接池中的最大空闲连接</span></span><br><span class="line"><span class="attr">spring.redis.pool.max-idle</span>=<span class="string">8</span></span><br><span class="line"><span class="comment"># 连接池中的最小空闲连接</span></span><br><span class="line"><span class="attr">spring.redis.pool.min-idle</span>=<span class="string">0</span></span><br><span class="line"><span class="comment"># 连接超时时间（毫秒）</span></span><br><span class="line"><span class="attr">spring.redis.timeout</span>=<span class="string">0</span></span><br></pre></td></tr></table></figure>

<p>Spring Boot 提供默认的 RedisTemplate 工具类根据配置文件自动连接 Redis，自动加载后可以直接调用其中的方法去操作。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RunWith(SpringJUnit4ClassRunner.class)</span></span><br><span class="line"><span class="meta">@SpringBootTest()</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ApplicationTests</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> RedisTemplate redisTemplate;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">test</span><span class="params">()</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">User</span>();</span><br><span class="line">        user.setName(<span class="string">&quot;我没有三颗心脏&quot;</span>);</span><br><span class="line">        user.setAge(<span class="number">21</span>);</span><br><span class="line">        <span class="comment">// 调用工具类方法</span></span><br><span class="line">        redisTemplate.opsForValue().set(<span class="string">&quot;user_1&quot;</span>, user);</span><br><span class="line">        <span class="type">User</span> <span class="variable">user1</span> <span class="operator">=</span> (User) redisTemplate.opsForValue().get(<span class="string">&quot;user_1&quot;</span>);</span><br><span class="line">        System.out.println(user1.getName());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><strong>RedisTemplate 类常用操作</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">redisTemplate.delete(key);                                   <span class="comment">// 删除 key</span></span><br><span class="line">redisTemplate.delete(keys);                                  <span class="comment">// 批量删除 key</span></span><br><span class="line">redisTemplate.expire(key,time,TimeUnit.MINUTES);             <span class="comment">// 设置 key 失效时间</span></span><br><span class="line"><span class="type">Long</span> <span class="variable">expire</span> <span class="operator">=</span> redisTemplate.getExpire(key);                  <span class="comment">// 获取 key 失效时间</span></span><br></pre></td></tr></table></figure>

<h3 id="Lettuce-客户端"><a href="#Lettuce-客户端" class="headerlink" title="Lettuce 客户端"></a>Lettuce 客户端</h3><p>更加高级的 Redis 客户端，用于线程安全同步，异步和响应使用，支持集群，Sentinel，管道和编码器。</p>
<ul>
<li>基于 Netty 框架的事件驱动的通信层，其方法调用是异步的。不用浪费线程等待网络或磁盘 I/O。</li>
<li>Lettuce 的 API 是线程安全的，所以可以操作单个 Lettuce 连接来完成各种操作。</li>
</ul>
<ol>
<li><strong>导入依赖</strong></li>
</ol>
<p>在 spring boot 2.x 版本，为 Redis 默认导入了 Lettuce 。</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!-- Redis 默认导入 Lettuce --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-data-redis<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p>如果 Spring Boot 版本过低，也可以自行导入 Lettuce. Redis 版本至少需要 2.6 .</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!-- 单独导入 Lettuce --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-data-redis<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>io.lettuce<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>lettuce-core<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>5.1.8.RELEASE<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure>

<ol start="2">
<li><strong>基本使用</strong></li>
</ol>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LettuceTest</span> &#123;</span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">testSetGet</span><span class="params">()</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="comment">// 注册连接信息</span></span><br><span class="line">        <span class="type">RedisURI</span> <span class="variable">redisUri</span> <span class="operator">=</span> RedisURI.builder()                    </span><br><span class="line">                .withHost(<span class="string">&quot;localhost&quot;</span>)</span><br><span class="line">                .withPort(<span class="number">6379</span>)</span><br><span class="line">                .withTimeout(Duration.of(<span class="number">10</span>, ChronoUnit.SECONDS))</span><br><span class="line">                .build();</span><br><span class="line">        <span class="comment">// 创建 Redis 客户端</span></span><br><span class="line">        <span class="type">RedisClient</span> <span class="variable">redisClient</span> <span class="operator">=</span> RedisClient.create(redisUri);   </span><br><span class="line">        <span class="comment">// 创建连接</span></span><br><span class="line">        StatefulRedisConnection&lt;String, String&gt; connection = redisClient.connect();     </span><br><span class="line">        <span class="comment">// 创建同步命令</span></span><br><span class="line">        RedisCommands&lt;String, String&gt; redisCommands = connection.sync();                </span><br><span class="line">        <span class="type">SetArgs</span> <span class="variable">setArgs</span> <span class="operator">=</span> SetArgs.Builder.nx().ex(<span class="number">5</span>);</span><br><span class="line">        <span class="type">String</span> <span class="variable">result</span> <span class="operator">=</span> redisCommands.set(<span class="string">&quot;name&quot;</span>, <span class="string">&quot;throwable&quot;</span>, setArgs);</span><br><span class="line">        Assertions.assertThat(result).isEqualToIgnoringCase(<span class="string">&quot;OK&quot;</span>);</span><br><span class="line">        result = redisCommands.get(<span class="string">&quot;name&quot;</span>);</span><br><span class="line">        Assertions.assertThat(result).isEqualTo(<span class="string">&quot;throwable&quot;</span>);</span><br><span class="line">        <span class="comment">/******************** 其他操作 **********************/</span></span><br><span class="line">        connection.close();                     <span class="comment">// 关闭连接</span></span><br><span class="line">        redisClient.shutdown();                 <span class="comment">// 关闭客户端</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>Lettuce 主要提供三种API：同步（sync）<code>RedisCommands</code>、异步（async）<code>RedisAsyncCommands</code>、反应式（reactive）<code>RedisReactiveCommands</code>。</p>
<ol start="3">
<li><strong>Spring Boot 集成</strong></li>
</ol>
<p>同样在配置文件中配置好参数。</p>
<figure class="highlight properties"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">spring.redis.host</span>=<span class="string">localhost</span></span><br><span class="line"><span class="attr">spring.redis.port</span>=<span class="string">6379</span></span><br><span class="line"><span class="attr">spring.redis.password</span>=<span class="string">root</span></span><br><span class="line"><span class="comment"># 连接池最大连接数(使用负值表示没有限制) 默认为8</span></span><br><span class="line"><span class="attr">spring.redis.lettuce.pool.max-active</span>=<span class="string">8</span></span><br><span class="line"><span class="comment"># 连接池最大阻塞等待时间(使用负值表示没有限制) 默认为-1</span></span><br><span class="line"><span class="attr">spring.redis.lettuce.pool.max-wait</span>=<span class="string">-1ms</span></span><br><span class="line"><span class="comment"># 连接池中的最大空闲连接 默认为8</span></span><br><span class="line"><span class="attr">spring.redis.lettuce.pool.max-idle</span>=<span class="string">8</span></span><br><span class="line"><span class="comment"># 连接池中的最小空闲连接 默认为 0</span></span><br><span class="line"><span class="attr">spring.redis.lettuce.pool.min-idle</span>=<span class="string">0</span></span><br></pre></td></tr></table></figure>

<p>我们同样可以使用 Spring Boot 提供默认的 RedisTemplate 工具类根据配置文件自动连接 Redis。但默认情况下的模板只支持 <code>RedisTemplate&lt;String,String&gt;</code> 存入字符串，因此我们往往需要自定义 RedisTemplate 设置序列化器，以方便操作实例对象。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RedisConfig</span> &#123;</span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> RedisTemplate <span class="title function_">redisTemplate</span><span class="params">(RedisConnectionFactory factory)</span> &#123;</span><br><span class="line">        RedisTemplate&lt;String, Serializable&gt; redisTemplate = <span class="keyword">new</span> <span class="title class_">RedisTemplate</span>&lt;&gt;();</span><br><span class="line">        <span class="comment">// key 采用 String 的序列化方式</span></span><br><span class="line">        redisTemplate.setKeySerializer(<span class="keyword">new</span> <span class="title class_">StringRedisSerializer</span>());</span><br><span class="line">        <span class="comment">// value 采用 jackson 的序列化方式</span></span><br><span class="line">        redisTemplate.setValueSerializer(<span class="keyword">new</span> <span class="title class_">GenericJackson2JsonRedisSerializer</span>());</span><br><span class="line">        <span class="comment">// hash 采用 String/jackson 的序列化方式</span></span><br><span class="line">        redisTemplate.setHashKeySerializer(stringRedisSerializer);</span><br><span class="line">        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);</span><br><span class="line">        redisTemplate.setConnectionFactory(connectionFactory);</span><br><span class="line">        <span class="keyword">return</span> redisTemplate;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>完成后即可用自定义的 RedisTemplate 工具类对 Redis 进行操作。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RunWith(SpringRunner.class)</span></span><br><span class="line"><span class="meta">@SpringBootTest</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RedisTest</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> RedisTemplate&lt;String, Serializable&gt; redisTemplate;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">test</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;user:1&quot;</span>;</span><br><span class="line">        redisTemplate.opsForValue().set(key, <span class="keyword">new</span> <span class="title class_">User</span>(<span class="number">1</span>,<span class="string">&quot;pjmike&quot;</span>,<span class="number">20</span>));</span><br><span class="line">        <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> (User) redisTemplate.opsForValue().get(key);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<hr>
<h2 id="参考链接"><a href="#参考链接" class="headerlink" title="参考链接"></a>参考链接</h2><ul>
<li>Lettuce<pre><code>  https://www.cnblogs.com/throwable/p/11601538.html
  https://juejin.im/post/6844903681087930375
</code></pre>
</li>
</ul>
 
      <!-- reward -->
      
      <div id="reword-out">
        <div id="reward-btn">
          打赏
        </div>
      </div>
      
    </div>
    

    <!-- copyright -->
    
    <div class="declare">
      <ul class="post-copyright">
        <li>
          <i class="ri-copyright-line"></i>
          <strong>版权声明： </strong>
          
          本博客所有文章除特别声明外，著作权归作者所有。转载请注明出处！
          
        </li>
      </ul>
    </div>
    
    <footer class="article-footer">
       
<div class="share-btn">
      <span class="share-sns share-outer">
        <i class="ri-share-forward-line"></i>
        分享
      </span>
      <div class="share-wrap">
        <i class="arrow"></i>
        <div class="share-icons">
          
          <a class="weibo share-sns" href="javascript:;" data-type="weibo">
            <i class="ri-weibo-fill"></i>
          </a>
          <a class="weixin share-sns wxFab" href="javascript:;" data-type="weixin">
            <i class="ri-wechat-fill"></i>
          </a>
          <a class="qq share-sns" href="javascript:;" data-type="qq">
            <i class="ri-qq-fill"></i>
          </a>
          <a class="douban share-sns" href="javascript:;" data-type="douban">
            <i class="ri-douban-line"></i>
          </a>
          <!-- <a class="qzone share-sns" href="javascript:;" data-type="qzone">
            <i class="icon icon-qzone"></i>
          </a> -->
          
          <a class="facebook share-sns" href="javascript:;" data-type="facebook">
            <i class="ri-facebook-circle-fill"></i>
          </a>
          <a class="twitter share-sns" href="javascript:;" data-type="twitter">
            <i class="ri-twitter-fill"></i>
          </a>
          <a class="google share-sns" href="javascript:;" data-type="google">
            <i class="ri-google-fill"></i>
          </a>
        </div>
      </div>
</div>

<div class="wx-share-modal">
    <a class="modal-close" href="javascript:;"><i class="ri-close-circle-line"></i></a>
    <p>扫一扫，分享到微信</p>
    <div class="wx-qrcode">
      <img src="//api.qrserver.com/v1/create-qr-code/?size=150x150&data=https://xlw686.github.io/2023/05/14/%E5%85%B6%E5%AE%83/%E6%95%B0%E6%8D%AE%E5%BA%93/Redis/" alt="微信分享二维码">
    </div>
</div>

<div id="share-mask"></div>  
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/yan/" rel="tag">yan</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/%E5%85%B6%E5%AE%83/" rel="tag">其它</a></li></ul>

    </footer>
  </div>

   
  <nav class="article-nav">
    
      <a href="/2023/05/14/%E5%85%B6%E5%AE%83/%E6%95%B0%E6%8D%AE%E5%BA%93/MySQL2/" class="article-nav-link">
        <strong class="article-nav-caption">上一篇</strong>
        <div class="article-nav-title">
          
            MySQL2
          
        </div>
      </a>
    
    
      <a href="/2023/05/14/%E5%85%B6%E5%AE%83/%E6%95%B0%E6%8D%AE%E5%BA%93/MySQL3/" class="article-nav-link">
        <strong class="article-nav-caption">下一篇</strong>
        <div class="article-nav-title">MySQL3</div>
      </a>
    
  </nav>

  
   
  
   
    <script src="https://cdn.staticfile.org/twikoo/1.4.18/twikoo.all.min.js"></script>
    <div id="twikoo" class="twikoo"></div>
    <script>
        twikoo.init({
            envId: ""
        })
    </script>
 
</article>

</section>
      <footer class="footer">
  <div class="outer">
    <ul>
      <li>
        Copyrights &copy;
        2023
        <i class="ri-heart-fill heart_icon"></i> 空唤晴
      </li>
    </ul>
    <ul>
      <li>
        
      </li>
    </ul>
    <ul>
      <li>
        
        
        <span>
  <span><i class="ri-user-3-fill"></i>访问人数:<span id="busuanzi_value_site_uv"></span></span>
  <span class="division">|</span>
  <span><i class="ri-eye-fill"></i>浏览次数:<span id="busuanzi_value_page_pv"></span></span>
</span>
        
      </li>
    </ul>
    <ul>
      
    </ul>
    <ul>
      
    </ul>
    <ul>
      <li>
        <!-- cnzz统计 -->
        
      </li>
    </ul>
  </div>
</footer>    
    </main>
    <div class="float_btns">
      <div class="totop" id="totop">
  <i class="ri-arrow-up-line"></i>
</div>

<div class="todark" id="todark">
  <i class="ri-moon-line"></i>
</div>

    </div>
    <aside class="sidebar on">
      <button class="navbar-toggle"></button>
<nav class="navbar">
  
  <div class="logo">
    <a href="/"><img src="/images/ayer-side.svg" alt="空唤晴"></a>
  </div>
  
  <ul class="nav nav-main">
    
    <li class="nav-item">
      <a class="nav-item-link" href="/">主页</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/archives">归档</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/categories">分类</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags">标签</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/friends">友链</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/2023/01/01/about">关于我</a>
    </li>
    
  </ul>
</nav>
<nav class="navbar navbar-bottom">
  <ul class="nav">
    <li class="nav-item">
      
      <a class="nav-item-link nav-item-search"  title="搜索">
        <i class="ri-search-line"></i>
      </a>
      
      
      <a class="nav-item-link" target="_blank" href="/atom.xml" title="RSS Feed">
        <i class="ri-rss-line"></i>
      </a>
      
    </li>
  </ul>
</nav>
<div class="search-form-wrap">
  <div class="local-search local-search-plugin">
  <input type="search" id="local-search-input" class="local-search-input" placeholder="Search...">
  <div id="local-search-result" class="local-search-result"></div>
</div>
</div>
    </aside>
    <div id="mask"></div>

<!-- #reward -->
<div id="reward">
  <span class="close"><i class="ri-close-line"></i></span>
  <p class="reward-p"><i class="ri-cup-line"></i>请我喝杯咖啡吧~</p>
  <div class="reward-box">
    
    <div class="reward-item">
      <img class="reward-img" src="/images/alipay.jpg">
      <span class="reward-type">支付宝</span>
    </div>
    
    
    <div class="reward-item">
      <img class="reward-img" src="/images/wechat.jpg">
      <span class="reward-type">微信</span>
    </div>
    
  </div>
</div>
    
<script src="/js/jquery-3.6.0.min.js"></script>
 
<script src="/js/lazyload.min.js"></script>

<!-- Tocbot -->
 
<script src="/js/tocbot.min.js"></script>

<script>
  tocbot.init({
    tocSelector: ".tocbot",
    contentSelector: ".article-entry",
    headingSelector: "h1, h2, h3, h4, h5, h6",
    hasInnerContainers: true,
    scrollSmooth: true,
    scrollContainer: "main",
    positionFixedSelector: ".tocbot",
    positionFixedClass: "is-position-fixed",
    fixedSidebarOffset: "auto",
  });
</script>

<script src="https://cdn.staticfile.org/jquery-modal/0.9.2/jquery.modal.min.js"></script>
<link
  rel="stylesheet"
  href="https://cdn.staticfile.org/jquery-modal/0.9.2/jquery.modal.min.css"
/>
<script src="https://cdn.staticfile.org/justifiedGallery/3.8.1/js/jquery.justifiedGallery.min.js"></script>

<script src="/dist/main.js"></script>

<!-- ImageViewer -->
 <!-- Root element of PhotoSwipe. Must have class pswp. -->
<div class="pswp" tabindex="-1" role="dialog" aria-hidden="true">

    <!-- Background of PhotoSwipe. 
         It's a separate element as animating opacity is faster than rgba(). -->
    <div class="pswp__bg"></div>

    <!-- Slides wrapper with overflow:hidden. -->
    <div class="pswp__scroll-wrap">

        <!-- Container that holds slides. 
            PhotoSwipe keeps only 3 of them in the DOM to save memory.
            Don't modify these 3 pswp__item elements, data is added later on. -->
        <div class="pswp__container">
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
        </div>

        <!-- Default (PhotoSwipeUI_Default) interface on top of sliding area. Can be changed. -->
        <div class="pswp__ui pswp__ui--hidden">

            <div class="pswp__top-bar">

                <!--  Controls are self-explanatory. Order can be changed. -->

                <div class="pswp__counter"></div>

                <button class="pswp__button pswp__button--close" title="Close (Esc)"></button>

                <button class="pswp__button pswp__button--share" style="display:none" title="Share"></button>

                <button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button>

                <button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button>

                <!-- Preloader demo http://codepen.io/dimsemenov/pen/yyBWoR -->
                <!-- element will get class pswp__preloader--active when preloader is running -->
                <div class="pswp__preloader">
                    <div class="pswp__preloader__icn">
                        <div class="pswp__preloader__cut">
                            <div class="pswp__preloader__donut"></div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
                <div class="pswp__share-tooltip"></div>
            </div>

            <button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)">
            </button>

            <button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)">
            </button>

            <div class="pswp__caption">
                <div class="pswp__caption__center"></div>
            </div>

        </div>

    </div>

</div>

<link rel="stylesheet" href="https://cdn.staticfile.org/photoswipe/4.1.3/photoswipe.min.css">
<link rel="stylesheet" href="https://cdn.staticfile.org/photoswipe/4.1.3/default-skin/default-skin.min.css">
<script src="https://cdn.staticfile.org/photoswipe/4.1.3/photoswipe.min.js"></script>
<script src="https://cdn.staticfile.org/photoswipe/4.1.3/photoswipe-ui-default.min.js"></script>

<script>
    function viewer_init() {
        let pswpElement = document.querySelectorAll('.pswp')[0];
        let $imgArr = document.querySelectorAll(('.article-entry img:not(.reward-img)'))

        $imgArr.forEach(($em, i) => {
            $em.onclick = () => {
                // slider展开状态
                // todo: 这样不好，后面改成状态
                if (document.querySelector('.left-col.show')) return
                let items = []
                $imgArr.forEach(($em2, i2) => {
                    let img = $em2.getAttribute('data-idx', i2)
                    let src = $em2.getAttribute('data-target') || $em2.getAttribute('src')
                    let title = $em2.getAttribute('alt')
                    // 获得原图尺寸
                    const image = new Image()
                    image.src = src
                    items.push({
                        src: src,
                        w: image.width || $em2.width,
                        h: image.height || $em2.height,
                        title: title
                    })
                })
                var gallery = new PhotoSwipe(pswpElement, PhotoSwipeUI_Default, items, {
                    index: parseInt(i)
                });
                gallery.init()
            }
        })
    }
    viewer_init()
</script> 
<!-- MathJax -->

<!-- Katex -->

<!-- busuanzi  -->
 
<script src="/js/busuanzi-2.3.pure.min.js"></script>
 
<!-- ClickLove -->

<!-- ClickBoom1 -->

<script src="https://cdn.staticfile.org/animejs/3.2.1/anime.min.js"></script>

<script src="/js/clickBoom1.js"></script>
 
<!-- ClickBoom2 -->

<!-- CodeCopy -->
 
<link rel="stylesheet" href="/css/clipboard.css">
 <script src="https://cdn.staticfile.org/clipboard.js/2.0.10/clipboard.min.js"></script>
<script>
  function wait(callback, seconds) {
    var timelag = null;
    timelag = window.setTimeout(callback, seconds);
  }
  !function (e, t, a) {
    var initCopyCode = function(){
      var copyHtml = '';
      copyHtml += '<button class="btn-copy" data-clipboard-snippet="">';
      copyHtml += '<i class="ri-file-copy-2-line"></i><span>COPY</span>';
      copyHtml += '</button>';
      $(".highlight .code pre").before(copyHtml);
      $(".article pre code").before(copyHtml);
      var clipboard = new ClipboardJS('.btn-copy', {
        target: function(trigger) {
          return trigger.nextElementSibling;
        }
      });
      clipboard.on('success', function(e) {
        let $btn = $(e.trigger);
        $btn.addClass('copied');
        let $icon = $($btn.find('i'));
        $icon.removeClass('ri-file-copy-2-line');
        $icon.addClass('ri-checkbox-circle-line');
        let $span = $($btn.find('span'));
        $span[0].innerText = 'COPIED';
        
        wait(function () { // 等待两秒钟后恢复
          $icon.removeClass('ri-checkbox-circle-line');
          $icon.addClass('ri-file-copy-2-line');
          $span[0].innerText = 'COPY';
        }, 2000);
      });
      clipboard.on('error', function(e) {
        e.clearSelection();
        let $btn = $(e.trigger);
        $btn.addClass('copy-failed');
        let $icon = $($btn.find('i'));
        $icon.removeClass('ri-file-copy-2-line');
        $icon.addClass('ri-time-line');
        let $span = $($btn.find('span'));
        $span[0].innerText = 'COPY FAILED';
        
        wait(function () { // 等待两秒钟后恢复
          $icon.removeClass('ri-time-line');
          $icon.addClass('ri-file-copy-2-line');
          $span[0].innerText = 'COPY';
        }, 2000);
      });
    }
    initCopyCode();
  }(window, document);
</script>
 
<!-- CanvasBackground -->
 
<script src="/js/dz.js"></script>
 
<script>
  if (window.mermaid) {
    mermaid.initialize({ theme: "forest" });
  }
</script>


    
    

  </div>
</body>

</html>